package cn.funnymap.lgis.tools.vector.generator.graphic;

import cn.funnymap.lgis.tools.exception.ShpGeneratorValidationException;
import cn.funnymap.lgis.tools.util.DateUtil;
import cn.funnymap.lgis.tools.vector.generator.GeneratorContent;
import cn.funnymap.lgis.tools.vector.generator.ShpField;
import cn.funnymap.lgis.tools.vector.generator.ShpGenerator;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;

/**
 * GeoJSON-Shapefile生成器
 *
 * @author jiaoxn
 */
public class EsriGraphic2ShpGenerator extends ShpGenerator {
    private String geometryType;
    private final EsriGraphic2ShpGeneratorParam generatorParam;

    public EsriGraphic2ShpGenerator(Path shpFilePath, EsriGraphic2ShpGeneratorParam generatorParam) {
        super(shpFilePath);

        this.generatorParam = generatorParam;
        this.validateGeneratorParam();
    }
    private void validateGeneratorParam() {
        JSONArray geojsonList = generatorParam.getGeojsonList();
        if (geojsonList.isEmpty()) {
            throw new ShpGeneratorValidationException("GeoJSON数据列表不可为空，请检查输入数据");
        }

        for (Object object : geojsonList) {
            JSONObject geojson = (JSONObject) object;

            // 判断属性字段是否存在
            this.validateAttributeField(geojson);

            // 判断几何字段是否存在
            if (geojson.containsKey("geometry")) {
                JSONObject geometry = geojson.getJSONObject("geometry");
                if (geometry.containsKey("type")) {
                    if (geometryType != null && !geometryType.equalsIgnoreCase(geometry.getString("type"))) {
                        throw new ShpGeneratorValidationException("GeoJSON几何类型不一致，请检查输入参数");
                    } else {
                        this.geometryType = geometry.getString("type");
                    }
                }
            } else {
                throw new ShpGeneratorValidationException("geometry字段不可为空，请检查输入数据");
            }
        }
    }
    private void validateAttributeField(JSONObject geojson) {
        if (!geojson.containsKey("attributes")) {
            throw new ShpGeneratorValidationException("attributes字段不可为空，请检查输入数据");
        }
    }

    @Override
    protected GeneratorContent buildGeneratorContent() {
        CoordinateReferenceSystem crs = generatorParam.getCrs();
        List<ShpField> fieldList = this.changeFileNameLength();
        List<HashMap<String, Object>> valueList = this.geojsonValueListFromGeneratorParam();
        List<Geometry> geometryList = this.geojsonGeneratorParam2Geometry();
        return new GeneratorContent(geometryList, fieldList, valueList, crs);
    }
    private List<ShpField> changeFileNameLength() {
        return this.generatorParam.getFieldList()
                .stream()
                .map(item -> new ShpField(cutFieldName(item.getName()), item.getAlias(), item.getType()))
                .collect(Collectors.toList());
    }
    private List<HashMap<String, Object>> geojsonValueListFromGeneratorParam() {
        List<HashMap<String, Object>> geojsonValueList = new ArrayList<>();

        JSONArray geojsonList = generatorParam.getGeojsonList();
        for (JSONObject geojson : geojsonList.toArray(JSONObject.class)) {
            HashMap<String, Object> geojsonValue = new LinkedHashMap<>();
            JSONObject geojsonAttribute = geojson.getJSONObject("attributes");

            for (ShpField shpField : this.generatorParam.getFieldList()) {
                String fieldName = shpField.getName();
                Object fieldValue = geojsonAttribute.get(fieldName);

                // 如果对应的值为字符串类型，优先判断是否为日期格式，如果是则替换原来的值
                if (fieldValue.getClass().equals(String.class)) {
                    Date date = DateUtil.parse((String) fieldValue);
                    if (date != null) {
                        fieldValue = date;
                    }
                }

                fieldName = cutFieldName(fieldName);

                geojsonValue.put(fieldName, fieldValue);
            }

            geojsonValueList.add(geojsonValue);
        }

        return geojsonValueList;
    }
    private List<Geometry> geojsonGeneratorParam2Geometry() {
        return Arrays.stream(this.generatorParam.getGeojsonList().toArray())
                .map(item -> this.fromGeometryJsonObject((JSONObject) item))
                .collect(Collectors.toList());
    }
    private Geometry fromGeometryJsonObject(JSONObject geojson) {
        try {
            JSONObject geometry = geojson.getJSONObject("geometry");
            switch (this.geometryType) {
                case "point":
                    return toPoint(geometry);
                case "polyline":
                    return toLine(geometry);
                case "polygon":
                    return toPolygon(geometry);
                default:
                    throw new ShpGeneratorValidationException("请检查几何类型");
            }
        } catch (ParseException e) {
            throw new ShpGeneratorValidationException("geometry转换失败，请检查输入数据");
        }
    }
    private Geometry toPoint(JSONObject geometryJson) throws ParseException {
        String wkt = String.format("POINT(%s %s)", geometryJson.getString("x"), geometryJson.getString("y"));
        int wkid = geometryJson.getJSONObject("spatialReference").getIntValue("wkid");
        return this.wkt2geometry(wkt, wkid);
    }
    private Geometry toLine(JSONObject geometryJson) throws ParseException {
        List<String> wholePathList = new ArrayList<>();

        JSONArray a = geometryJson.getJSONArray("paths");
        for (int i = 0; i < a.size(); i++) {
            JSONArray b = a.getJSONArray(i);
            List<String> tempPathList = new ArrayList<>();
            for (int j = 0; j < b.size(); j++) {
                JSONArray c = b.getJSONArray(j);
                tempPathList.add(String.format("%s %s", c.getString(0), c.getString(1)));
            }
            wholePathList.add(String.format("(%s)", StringUtils.join(tempPathList, ", ")));
        }

        String lineWkt;
        if (wholePathList.size() == 1) {
            lineWkt = String.format("LINESTRING%s", wholePathList.get(0));
        } else {
            lineWkt = String.format("MULTILINESTRING(%s)", StringUtils.join(wholePathList, ", "));
        }
        int wkid = geometryJson.getJSONObject("spatialReference").getIntValue("wkid");
        return wkt2geometry(lineWkt, wkid);
    }
    private Geometry toPolygon(JSONObject geometryJson) throws ParseException {
        List<String> wholePathList = new ArrayList<>();

        JSONArray a = geometryJson.getJSONArray("rings");
        for (int i = 0; i < a.size(); i++) {
            JSONArray b = a.getJSONArray(i);
            List<String> tempPathList = new ArrayList<>();
            for (int j = 0; j < b.size(); j++) {
                JSONArray c = b.getJSONArray(j);
                tempPathList.add(String.format("%s %s", c.getString(0), c.getString(1)));
            }
            wholePathList.add(String.format("(%s)", StringUtils.join(tempPathList, ", ")));
        }

        String polygonWkt;
        if (wholePathList.size() == 1) {
            polygonWkt = String.format("POLYGON(%s)", wholePathList.get(0));
        } else {
            polygonWkt = String.format("MULTIPOLYGON(%s)", StringUtils.join(wholePathList, ", "));
        }
        int wkid = geometryJson.getJSONObject("spatialReference").getIntValue("wkid");
        return wkt2geometry(polygonWkt, wkid);
    }
    private Geometry wkt2geometry(String wkt, int wkid) throws ParseException {
        GeometryFactory geometryFactory = JTSFactoryFinder.getGeometryFactory();
        WKTReader reader = new WKTReader(geometryFactory);
        Geometry geometry = reader.read(wkt);
        geometry.setSRID(wkid);
        return geometry;
    }

    @Override
    protected Class<?> geometryType() {
        switch (this.geometryType) {
            case "point":
                return Point.class;
            case "polyline":
                return LineString.class;
            case "polygon":
                return Polygon.class;
            default:
                throw new ShpGeneratorValidationException("请检查几何类型");
        }
    }
}
